<?php
require_once 'Database.php';

logAuthenticateEvent(51, "enter authenticate page before require_once directives");

require_once 'AuthenticationRequest.php';
require_once 'AuthenticationResponse.php';
require_once 'AirTime.php';

require_once 'SecureSoapServer.php';
require_once 'ServerHeaderDOMDocument.php';
logAuthenticateEvent(51, "enter authenticate page after require_once directives");

require_once 'SecureSoapServer.php';

/*
 * log events to the database
 */
function logAuthenticateEvent($level, $description)
{
    try {
        $db = new Database();
        $db->connect();
        $db->logEvent("authenticate.php", $level, $description);
    }
    catch (Exception $ee) {
        throw $ee;
        //if we're not logging, we're out of luck anyway.  
        //don't bother to do anything here.
    }
}

/*
 * return true if the user information provided in the
 * header is valid.  Return false otherwise.
 * The incoming request message is ignored.  It only exists
 * to distract potential attackers.
 */
function authenticate(AuthenticationRequest $requestMessage)
{
    try 
    {        
        $db = new Database();
        $db->connect();
        $db->logEvent('authenticate.php->authenticate()', 21, 'enter method');
        $dom = SecureSoapServer::GetDOM();

        $db->logEvent('authenticate.php->authenticate()', 41, 'DOM constructed');
        //These four entries come in on the client header:
        $clientUsername =  $dom->clientUsername;
        $callerUsername = $dom->callerUsername;

        $callerPassword = $dom->callerPassword;
        $clientPassword = $dom->clientPassword;;

        $rawHeaderFields = $callerUsername . $callerPassword . $clientUsername . $clientPassword;
        $db->logEvent(__CLASS__ . '::' . __METHOD__, 28, 'rawFields: ' . $rawHeaderFields);
        $hashLocal = hash('sha256', $rawHeaderFields);
        $hash = $dom->hash;
        $hashOK = (strcmp($hashLocal,$hash) === 0);

        $hashLocal = hash('sha256',$callerUsername . $callerPassword . $clientUsername . $clientPassword);
        $hashOK = (strcmp($hashLocal,$hash) === 0);
        if (hashOK)
        {
            $db->logEvent(__METHOD__, 4, 'hash OK: ' . $callerUsername);
            return authenticateInternal ($clientUsername, $callerUsername,
                    $clientPassword, $callerPassword);
        }
        else
        {
            $db->logEvent(__METHOD__, 4, 'hash mismatch: ' . $callerUsername);
            $isAuthenticated = 0;
        }
        return new AuthenticationResponse($isAuthenticated);
    }
    catch (Exception $ef)
    {
        $db = new Database();
        $db->connect();
        $db->logEvent(__METHOD__, 1, 'SOAP Fault :' . $ef->getMessage() );
        throw new AirIdentityException('authenticate');
    }
    catch (Exception $ee)
    {
        $db = new Database();
        $db->connect();
        $db->logEvent(__METHOD__, 1, 'Exception :' . $ee->getMessage() );
        throw new AirIdentityException('authenticate');
    }
}

/*
 * authenticateInternal - authenticate two users
 */
function authenticateInternal( $clientUsername, $callerUsername, 
        $clientPassword, $callerPassword ) 
    {
        $isAuthenticated = 0;
        try
        {
            $db = new Database();
            $db->connect();
            $callerOK = authenticateOne($callerUsername, $callerPassword);
            if (!$callerOK)
            {
                $db->logEvent(__CLASS__ . "::".  __METHOD__, 4, 'caller not authenticated: ' . 
                        $callerUsername);
            }
            $clientOK = authenticateOne($clientUsername, $clientPassword);
            if (!$clientOK)
            {
                $db->logEvent(__CLASS__ . "::".  __METHOD__, 4, 'client not authenticated: ' . $clientUsername);
            }
            if ($callerOK && $clientOK) {
                $isAuthenticated = 1;
            } else {
                $isAuthenticated = 0;
            }
        }
        catch (Exception $ee)
        {
            $isAuthenticated = 0;
        }
        return new AuthenticationResponse($isAuthenticated);
    }

/*
 * Authenticate one user by verifying that the encrypted "password" field contains
 * the serverNonce starting at index 36 and that the serverNonce matches the one 
 * stored for the given user.
 * Also verify that the timestamp starting immediately after the serverNonce field
 * is a valid and recent time.
 */
function authenticateOne($user, $password) {
    $db = new Database();
    $db->connect();
    $decodedPassword = base64_decode($password);
    $db->logEvent(__CLASS__ . "::".  __METHOD__, 28, 'decodedPassword: ' . $decodedPassword);
    $decryptedPassword = $db->aesDecrypt($decodedPassword, $user);
    $dbServerNonce = $db->getNonce($user);  
    $clientNonceLength = 36; 
    //strlen($dbClientNonce); -- users much provide a 36-character nonce
    //if the server nonces match, we proceed to check whether the time stamp is recent
    $serverNonceLength = strlen($dbServerNonce);
    $db->LogEvent(__CLASS__ . "::" . __METHOD__, 28, 'decryptedPassword: ' . $decryptedPassword );
    $secondPartOfCombinedNonce = 
        substr($decryptedPassword, $clientNonceLength, $serverNonceLength);
    $serverNoncesMatch = 
        (strcmp($dbServerNonce, $secondPartOfCombinedNonce) == 0);
    
    if (TRUE == $serverNoncesMatch)
    {
        $startingIndexOfTimeStamp = $clientNonceLength + $serverNonceLength;
        //the time stamp + any salt added to the end of it, so we have to use the timestamp length.
        $lengthOfTimeStamp = strlen('1234-67-90 23:56:89');  //ignore trailing characters.
        $thirdPartOfCombinedNonce = substr($decryptedPassword, $startingIndexOfTimeStamp,$lengthOfTimeStamp);
        $timeIsValidAndRecent = (AirTime::isRecentTimeString($thirdPartOfCombinedNonce));
        if (!$timeIsValidAndRecent)
        {
            $db->logEvent(__METHOD__, 4, 'time stamp is NOT valid and recent:'. 
                    $thirdPartOfCombinedNonce);
        }
        $result = $timeIsValidAndRecent;
    }
    else 
    {
        $db->logEvent(__METHOD__, 4, 'server nonce given ('. 
                $secondPartOfCombinedNonce . 
                ') does not match. I was supposed to get: ' . $dbServerNonce );
        $result = FALSE;
        }
    $db->logEvent(__METHOD__, 20, 'returning (' . $result . ') for ' . $user . 
            ". The password they sent was $secondPartOfCombinedNonce and the 
            server nonce to match was $dbServerNonce");
    return $result;
}
?>